package com.threeman.maper.map;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;

import com.threeman.maper.MainActivity;

/**
 * 
 * <b>AzimuthProvider</b>
 * <p>
 * Dostarcza aktualnego azymutu.
 * </p>
 * 
 * @author Marcin
 * @date 21-08-2011
 * @version 1.2
 * @since 1.0
 */
public class AzimuthProvider extends Thread
{

	private static final String TAG = "AzimuthThread";

	private Context mContext;

	private DirectionItem mDirectionItem;

	private SensorManager mSensorManager;

	private Sensor mSensorAccelerometer;

	private Sensor mSensorMagneticField;

	private boolean mSensorsOK = false;

	private boolean mRunning = false;

	private float mAzimuth = 0.0f;

	private float mAzimuthAvg = 0.0f;

	public static final int LONG_SPAN_MILLIS = 200;

	public static final int STD_SPAN_MILLIS = 40;


	public AzimuthProvider(Context context, DirectionItem directionItem)
	{
		this.setName("AzimuthThread");
		mContext = context;
		mDirectionItem = directionItem;
		try
		{
			mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
			mSensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
			mSensorMagneticField = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

			if (mSensorAccelerometer == null || mSensorMagneticField == null)
				mSensorsOK = false;
			else
			{
				boolean isPresentAcc = mSensorManager.registerListener(mListener, mSensorAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
				boolean isPresentMag = mSensorManager.registerListener(mListener, mSensorMagneticField, SensorManager.SENSOR_DELAY_NORMAL);
				if (isPresentAcc && isPresentMag)
					mSensorsOK = true;
				else
					mSensorsOK = false;
				mSensorManager.unregisterListener(mListener, mSensorAccelerometer);
				mSensorManager.unregisterListener(mListener, mSensorMagneticField);
			}
		} catch (Exception ex)
		{
			Log.e(MainActivity.APPTAG, TAG + " Error ", ex);
		}
	}

	private final SensorEventListener mListener = new SensorEventListener() {

		// values
		private float[] mGravity = new float[] { 0, 0, 0 };

		private float[] mMagnetic = new float[] { 0, 0, 0 };

		float[] mValues = new float[3];

		float[] mR = new float[16]; // 9 lub 16

		float[] mI = new float[16]; // 9 lub 16


		public void onSensorChanged(SensorEvent event)
		{
			if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
				mGravity = event.values.clone();
			if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
				mMagnetic = event.values.clone();

			SensorManager.getRotationMatrix(mR, mI, mGravity, mMagnetic);
			// SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_Y, SensorManager.AXIS_X, mR);
			SensorManager.getOrientation(mR, mValues);

			mValues[0] = (float) Math.toDegrees(mValues[0]);
			mValues[1] = (float) Math.toDegrees(mValues[1]);
			mValues[2] = (float) Math.toDegrees(mValues[2]);

			float newAzimuth = mValues[0];

			float difference = newAzimuth - mAzimuthAvg;
			difference = normalizeAngle(difference);

			if (Math.abs(difference) < 5)
			{
				newAzimuth = mAzimuthAvg + difference / 4;
			} else
			{
				preferSensorListenerState(SENSOR_LISTENER_STATE_ACTION);
			}
			newAzimuth = normalizeAngle(newAzimuth);
			mAzimuthAvg = newAzimuth;

			mAzimuth = mAzimuthAvg;
		}


		public void onAccuracyChanged(Sensor sensor, int accuracy)
		{
		}
	};


	public boolean isSensorsOK()
	{
		return mSensorsOK;
	}


	public boolean isRunning()
	{
		return mRunning;
	}


	public void setRunning(boolean running)
	{
		this.mRunning = running;
	}


	@Override
	public void run()
	{
		Log.i(MainActivity.APPTAG, TAG + " : START");

		setRunning(true);

		boolean running = true;

		float speed = 0.0f;
		float lastAzimuth = 0.0f;

		boolean arrived = false;

		mSpeedMode = AzimuthProvider.SPEED_SLOW;

		setSensorListenerState(AzimuthProvider.SENSOR_LISTENER_STATE_ACTION);

		while (running)
		{
			if (!isRunning())
				break;

			float currentAzimuth = mAzimuth;

			boolean needPaint = calculateNeedPainting(arrived, speed, lastAzimuth, currentAzimuth);

			if (needPaint)
			{
				arrived = false;

				float difference = calculateNormalizedDifference(lastAzimuth, currentAzimuth);
				speed = calculateSpeed(difference, speed, mSpeedMode);
				currentAzimuth = lastAzimuth + speed;
				// Log.i(TAG, "currentAzimuth = " + currentAzimuth + " : lastAzimuth = " + lastAzimuth);
				lastAzimuth = currentAzimuth;

				mAzimuth = currentAzimuth;
				mDirectionItem.setRotation(mAzimuth);

			} else
			{
				arrived = true;
			}

			// Thread.sleep(LONG_SPAN_MILLIS);

			try
			{
				if (arrived)
				{
					// preferSensorListenerState(AzimuthProvider.SENSOR_LISTENER_STATE_SLEEP);
					try
					{
						Thread.sleep(AzimuthProvider.LONG_SPAN_MILLIS);
					} catch (InterruptedException ex)
					{
						Log.w(MainActivity.APPTAG, TAG + " : Unexpected long sleep", ex);
					}
				} else
				{
					// preferSensorListenerState(AzimuthProvider.SENSOR_LISTENER_STATE_ACTION);
					try
					{
						Thread.sleep(AzimuthProvider.STD_SPAN_MILLIS);
					} catch (InterruptedException ex)
					{
						Log.w(MainActivity.APPTAG, TAG + " : Unexpected sleep", ex);
					}
				}
			} catch (Throwable t)
			{
				Log.w(MainActivity.APPTAG, TAG + " : Unexpected", t);
			}

		}

		setSensorListenerState(AzimuthProvider.SENSOR_LISTENER_STATE_OFF);
		setRunning(false);
		Log.i(MainActivity.APPTAG, TAG + " : STOP");

	}

	private static final float ARRIVED_EPS = 0.7f; // tolerance to become arrived

	private static final float LEAVED_EPS = 2.5f; // tolerance to leave arrived

	private static final float SPEED_EPS = 0.5f; // tolerance of speed


	private boolean calculateNeedPainting(boolean arrived, float speed, float currentAzimuth, float nextAzimuth)
	{
		if (arrived)
		{
			if (Math.abs(currentAzimuth - nextAzimuth) > AzimuthProvider.LEAVED_EPS)
			{
				// Log.i(TAG, "leave : " + arrived + ", " + speed + ", " + currentAzimuth + ", " + nextAzimuth);
				return true;
			}
			return false;
		} else
		{
			if (Math.abs(currentAzimuth - nextAzimuth) > AzimuthProvider.ARRIVED_EPS && Math.abs(speed) < AzimuthProvider.SPEED_EPS)
			{
				// Log.i(TAG, "arrived : " + arrived + ", " + speed + ", " + currentAzimuth + ", " + nextAzimuth);
				return false;
			}
			return true;
		}
	}

	// Possible speeds for needle movement
	public static final int SPEED_SLOW = 0;

	public static final int SPEED_NORMAL = 1;

	public static final int SPEED_FAST = 2;

	public static final int SPEED_DIRECT = 3;

	public static final int SPEED_SWING = 4;

	private int mSpeedMode = SPEED_NORMAL;


	/**
	 * calculates the new speed the needle moves to the setpoint
	 * 
	 * @param diff
	 *            distance to the setpoint
	 * @param oldSpeed
	 * @param speedMode
	 *            determines which physics to use
	 * @return the new speed
	 */
	private float calculateSpeed(float diff, float oldSpeed, int speedMode)
	{
		switch ( speedMode )
		{
			case SPEED_DIRECT :
			{
				oldSpeed = oldSpeed * 0f; // friction
				oldSpeed += diff; // acceleration
				return oldSpeed;
			}
			case SPEED_FAST :
			{
				oldSpeed = oldSpeed * 0.75f; // friction
				oldSpeed += diff / 8.0f; // acceleration
				return oldSpeed;
			}
			case SPEED_SLOW :
			{
				oldSpeed = oldSpeed * 0.75f; // friction
				oldSpeed += diff / 40.0f; // acceleration
				return oldSpeed;
			}

			case SPEED_SWING :
			{
				oldSpeed = oldSpeed * 0.97f; // friction
				oldSpeed += diff / 10.0f; // acceleration
				return oldSpeed;
			}

			case SPEED_NORMAL :
			default :
			{
				oldSpeed = oldSpeed * 0.75f; // friction
				oldSpeed += diff / 20.0f; // acceleration
				return oldSpeed;
			}
		}
	}

	public static final int SENSOR_LISTENER_STATE_OFF = 0;

	public static final int SENSOR_LISTENER_STATE_SLEEP = 1;

	public static final int SENSOR_LISTENER_STATE_ACTION = 2;

	private int mSensorListenerState = SENSOR_LISTENER_STATE_OFF;


	public void setSensorListenerState(int newState)
	{
		if (newState == mSensorListenerState)
			return;

		mSensorManager.unregisterListener(mListener, mSensorAccelerometer);
		mSensorManager.unregisterListener(mListener, mSensorMagneticField);
		mSensorListenerState = AzimuthProvider.SENSOR_LISTENER_STATE_OFF;

		// Log.i(TAG, "sensor state --> SENSOR_LISTENER_STATE_OFF");

		if (newState == AzimuthProvider.SENSOR_LISTENER_STATE_SLEEP)
		{
			mSensorManager.registerListener(mListener, mSensorAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
			mSensorManager.registerListener(mListener, mSensorMagneticField, SensorManager.SENSOR_DELAY_NORMAL);
			mSensorListenerState = newState;
			// Log.i(TAG, "sensor state --> SENSOR_LISTENER_STATE_SLEEP");
		}
		if (newState == AzimuthProvider.SENSOR_LISTENER_STATE_ACTION)
		{
			mSensorManager.registerListener(mListener, mSensorAccelerometer, SensorManager.SENSOR_DELAY_UI);
			mSensorManager.registerListener(mListener, mSensorMagneticField, SensorManager.SENSOR_DELAY_UI);
			mSensorListenerState = newState;
			// Log.i(TAG, "sensor state --> SENSOR_LISTENER_STATE_ACTION");
		}
	}


	public void preferSensorListenerState(int newState)
	{
		if (mSensorListenerState != AzimuthProvider.SENSOR_LISTENER_STATE_OFF)
		{
			setSensorListenerState(newState);
		}
	}


	private static float normalizeAngle(float difference)
	{
		while (difference < -180)
		{
			difference += 360;
		}
		while (difference > 180)
		{
			difference -= 360;
		}
		return difference;
	}


	private static float calculateNormalizedDifference(float lastAzimuth, float currentAzimuth)
	{
		float difference = currentAzimuth - lastAzimuth;
		return normalizeAngle(difference);
	}
}
